package jspeech.sys;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.Socket;

import jspeech.exception.EngineRuntimeException;
import jcommand.CommandSet;

/**
 * This class is implemented as a thread. This will handle all the message and
 * exception received from the engines.
 * 
 * @author Kenneth
 * 
 */
public class SocketReader implements Runnable {

	/**
	 * This socket init when creating SocketReader object.
	 */
	private Socket clientSocket = null;

	/**
	 * The last message read from the socket. If the method
	 * <code>getSocketMessage()</code> is called, it will set to null
	 */
	private String message = null;

	/**
	 * The status of <code>clientSocket</code> object
	 */
	private boolean isRunning = false;

	/**
	 * If the socket containing a new message, this locking will enable until
	 * the update of <code>message</code> is completed. This can prevernt the
	 * issue of sync problem.
	 */
	private boolean lock = false;

	/**
	 * Command Set - Command that need to run
	 */
	private CommandSet commandSet;

	/**
	 * Run the recohnizer in background
	 */
	private boolean backgroundProcess;

	/**
	 * Read data to the socket (Engine to JSpeech)
	 */
	private BufferedReader in = null;

	/**
	 * If the debug mode is set to true, detail flow message will print out to
	 * the console
	 */
	private boolean debug_mode = false;

	/**
	 * Create a SocketReader object with specified <code>Socket</code>. If
	 * the backgoundProcess is set to true, when the socket received message
	 * beginning with COMMAND, it will execute to command.
	 * 
	 * @param clientSocket
	 *            the Socket
	 * @param backgroundProcess
	 *            true if the reader need to execute the command, otherwise
	 *            false
	 * @throws IOException
	 *             if an I/O error occurs when creating the socket.
	 */
	public SocketReader(Socket clientSocket, boolean backgroundProcess,
			boolean debug_mode) throws IOException {
		this(clientSocket, backgroundProcess, null, debug_mode);
	}

	/**
	 * Create a SocketReader object with specified <code>Socket</code> and
	 * <code>CommandSet</code>. If the backgoundProcess is set to true, when
	 * the socket received message beginning with COMMAND, it will execute to
	 * command.
	 * 
	 * @param clientSocket
	 *            the Socket
	 * @param backgroundProcess
	 *            true if the reader need to execute the command, otherwise
	 *            false
	 * @param commandSet
	 *            the command need to run. It created by JCommand API
	 *            (jcommand.CommandSet).
	 * @throws IOException
	 *             if an I/O error occurs when creating the socket.
	 * 
	 */
	public SocketReader(Socket clientSocket, boolean backgroundProcess,
			CommandSet commandSet, boolean debug_mode) throws IOException {
		this.commandSet = commandSet;
		this.clientSocket = clientSocket;
		this.backgroundProcess = backgroundProcess;
		this.debug_mode = debug_mode;
		in = new BufferedReader(new InputStreamReader(clientSocket
				.getInputStream()));
	}

	/**
	 * Get the last message receive from the socket. If no new message, it will
	 * return null. Besides, if the socket is updating the message, the message
	 * will be locked until the update is finished.
	 * 
	 * @return last socket received message
	 */
	public String getSocketMessage() {

		if (!lock) {
			String temp = message;
			message = null;
			return temp;
		} else
			return null;

	}

	/**
	 * Close the SocketReader object
	 * 
	 */
	public void close() {
		try {
			isRunning = false;
			clientSocket.close();
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Run the SocketReader obejct
	 */
	public void run() {

		isRunning = true;
		while (isRunning && clientSocket.isConnected()) {
			// Display info about this particular thread
			try {

				message = in.readLine();

				lock = true;
				if (this.debug_mode && message != null)
					System.out.println(" >>RECEIVED:" + message);

				// Catch the engine runtime exception
				if (message != null && message.startsWith("ERROR")) {

					// Closing the socket
					this.clientSocket.close();
					this.in.close();

					// Throw the exception
					throw new EngineRuntimeException(message.replaceAll(
							"ERROR:", ""));
				}

				// If background process is set to run, execute it
				if (backgroundProcess && message != null) {
					runCommand(message);
				}

			} catch (IOException e) {

			} finally {
				lock = false;
			}
		}
	}

	/**
	 * Run the command. If the received message is not start with <i>COMMAND</i>,
	 * it will return immediately.
	 * 
	 * @param message
	 *            the received socket message
	 */
	private void runCommand(String message) {
		if (!message.startsWith("COMMAND:"))
			return;

		try {
			if (this.debug_mode)
				System.out.println(message.replaceAll(
						">>>SocketReader.runCommand():COMMAND:", ""));

			if (commandSet == null)
				return;

			commandSet.runCommand(message.replaceAll("COMMAND:", ""));
		} catch (IOException e) {

		}
	}

	/**
	 * Get the background process
	 * 
	 * @return true if the reader will execute the command once receive the
	 *         message from socket, otherwise false
	 */
	public boolean isBackgroundProcessEnable() {
		return this.backgroundProcess;
	}

	/**
	 * Return the client socket
	 * 
	 * @return the Socket
	 */
	public Socket getClientSocket() {
		return this.clientSocket;
	}

	/**
	 * Return the command set object. For more information about
	 * <code>CommandSet</code>, please refer to JCommand API
	 * (jcommand.CommandSet).
	 * 
	 * @return the CommandSet
	 */
	public CommandSet getCommandSet() {
		return this.commandSet;
	}

	/**
	 * Set the CommandSet object. For more information about
	 * <code>CommandSet</code>, please refer to JCommand API
	 * (jcommand.CommandSet).
	 * 
	 * @param commandSet
	 *            CommandSet
	 */
	public void setCommandSet(CommandSet commandSet) {
		this.commandSet = commandSet;
	}

	/**
	 * Return the status of SocketReader
	 * 
	 * @return true if SocketReader is closedm otherwise false
	 */
	public boolean isClose() {
		return !this.isRunning;
	}
}
